package com.jservice.web.base;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.jfinal.aop.Before;
import com.jfinal.core.Controller;
import com.jfinal.core.JFinal;
import com.jfinal.ext.interceptor.NotAction;
import com.jfinal.i18n.Res;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.Table;
import com.jfinal.plugin.activerecord.TableMapping;
import com.jfinal.render.JsonRender;
import com.jfinal.upload.UploadFile;
import com.jservice.framework.authc.AuthcInterceptor;
import com.jservice.framework.dto.Filter;
import com.jservice.framework.dto.Message;
import com.jservice.framework.dto.Order;
import com.jservice.framework.dto.Pageable;
import com.jservice.framework.freemarker.directive.FlashMessageDirective;
import com.jservice.framework.jsession.JSession;
import com.jservice.framework.utils.*;
import com.jservice.model.SysUser;
import com.jservice.web.WebConstant;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by cwledit on 2017/5/24.
 */
@Before(AuthcInterceptor.class)
public class JBaseController extends Controller{

    private static final char URL_PARA_SEPARATOR = JFinal.me().getConstants().getUrlParaSeparator().toCharArray()[0];
    private JSession session;

    public JBaseController() {
        session = new JSession(this);
    }

    @Override
    public String getPara(String name) {
        return JsoupUtils.clear(getRequest().getParameter(name));
    }

    @Override
    public String getPara(String name, String defaultValue) {
        String html = getRequest().getParameter(name);
        if (null != html) {
            return JsoupUtils.clear(html);
        }
        return defaultValue;
    }

    public void redirectLogin(){
        redirect("/admin/authc/login");
    }

    private int mParaCount = -1;

    public int getParaCount() {
        if (mParaCount != -1)
            return mParaCount;

        mParaCount = 0;
        char[] parachars = getPara() == null ? null : getPara().toCharArray();
        if (parachars != null) {
            mParaCount = 1;
            for (char c : parachars) {
                if (URL_PARA_SEPARATOR == c) {
                    mParaCount++;
                }
            }
        }
        return mParaCount;
    }

    /**
     * 获取当前用户
     * @return
     */
    public SysUser getCUser(){
        return getSessionAttr(WebConstant.request_cUser);
    }

    /**
     * 是否是手机浏览器
     *
     * @return
     */
    public boolean isMoblieBrowser() {
        return RequestUtils.isMoblieBrowser(getRequest());
    }

    /**
     * 是否是微信浏览器
     *
     * @return
     */
    public boolean isWechatBrowser() {
        return RequestUtils.isWechatBrowser(getRequest());
    }

    /**
     * 是否是IE浏览器
     *
     * @return
     */
    public boolean isIEBrowser() {
        return RequestUtils.isIEBrowser(getRequest());
    }

    public boolean isAjaxRequest() {
        return RequestUtils.isAjaxRequest(getRequest());
    }

    public boolean isMultipartRequest() {
        return RequestUtils.isMultipartRequest(getRequest());
    }

    public void setHeader(String key, String value) {
        getResponse().setHeader(key, value);
    }

    public Res getI18nRes() {
        // Attribute set in JI18nInterceptor.class
        return getAttr("i18n");
    }

    public String getI18nValue(String key) {
        return getI18nRes().get(key);
    }

    @Override
    @Before(NotAction.class)
    public void createToken() {
        createToken("jtoken");
    }

    @Override
    public boolean validateToken() {
        return validateToken("jtoken");
    }

    @Override
    public HttpSession getSession() {
        return session;
    }

    @Override
    public HttpSession getSession(boolean create) {
        return getSession();
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getSessionAttr(String key) {
        return (T) session.getAttribute(key);
    }

    @Override
    public Controller setSessionAttr(String key, Object value) {
        session.setAttribute(key, value);
        return this;
    }

    @Override
    public Controller removeSessionAttr(String key) {
        session.removeAttribute(key);
        return this;
    }


    public HashMap<String, String> getUploadFilesMap() {
        List<UploadFile> fileList = null;
        if (isMultipartRequest()) {
            fileList = getFiles();
        }

        HashMap<String, String> filesMap = null;
        if (fileList != null) {
            filesMap = new HashMap<String, String>();
            for (UploadFile ufile : fileList) {
                String filePath = AttachmentUtils.moveFile(ufile).replace("\\", "/");
                filesMap.put(ufile.getParameterName(), filePath);
            }
        }
        return filesMap;
    }

    /** 错误视图 */
    protected static final String ERROR_VIEW = "/admin/common/error";

    protected void rendJson(Object json){
        String agent = getRequest().getHeader("User-Agent");
        if(agent!=null && agent.contains("MSIE"))
            this.render(new JsonRender(json).forIE());
        else{
            this.render(new JsonRender(json));
        }
    }

    @Override
    public void render(String view) {
        if(!org.apache.commons.lang3.StringUtils.endsWith(view,".html")){
            view = view +".html";
        }
        super.render(view);
    }

    /**
     * 设置FLASH MESSAGE
     * @param message
     */
    protected void setFlashMessage(Message message){
        WebUtils.addCookie(getRequest(),getResponse(), FlashMessageDirective.FLASH_MESSAGE_ATTRIBUTE_NAME,message.toString(),300);
    }

    protected void setMessage(Message message){
        setAttr(WebConstant.FLASH_MESSAGE,message);
    }


    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Model getModelExt(Class clz) throws Exception {
        HttpServletRequest request = this.getRequest();
        try{
            Model model_ = (Model) clz.newInstance();
            Table tableInfo = TableMapping.me().getTable(clz);
            model_._getAttrNames();
            Enumeration<String> attrNames = request.getParameterNames();
            while(attrNames.hasMoreElements()) {
                String attr = attrNames.nextElement();
                Class<?> colType = null;
                if (tableInfo.hasColumnLabel(attr.toLowerCase()))
                    colType = tableInfo.getColumnType(attr.toLowerCase());
                if (tableInfo.hasColumnLabel(attr.toUpperCase())) {
                    colType = tableInfo.getColumnType(attr.toUpperCase());
                }
                if (colType != null) {
                    String value = request.getParameter(attr);
                    model_.set(attr.toLowerCase(), value != null ? convert(colType, value): null);
                }
            }
            /**从request流中取json*/
            if(model_._getAttrValues()==null||model_._getAttrValues().length==0){
                String json = inputStream2String(this.getRequest().getInputStream());
                model_ = JSON.parseObject(json,model_.getClass());
            }
            return model_;
        }catch(Exception e){
            throw e;
        }
    }

    protected String inputStream2String(InputStream in) throws IOException {
        if(in == null)
            return "";
        StringBuffer out = new StringBuffer();
        byte[] b = new byte[4096];
        for (int n; (n = in.read(b)) != -1;) {
            out.append(new String(b, 0, n, "UTF-8"));
        }
        return out.toString();
    }

    public <T> List<T> getModels(Class<T> modelClass) {
        return getModels(modelClass, StrKit.firstCharToLowerCase(modelClass.getSimpleName()));
    }

    /**
     * 获取前端传来的数组对象并响应成Model列表
     *  <input type="text" name="model[0].name" value="0"/>
     *  <input type="text" name="model[1].name" value="1"/>
     *  <input type="text" name="model[2].name" value="2"/>
     */
    public <T> List<T> getModels(Class<T> modelClass, String modelName) {
        List<String> indexes = getIndexes(modelName);
        List<T> list = new ArrayList<T>();
        for (String index : indexes) {
            T m = getModel(modelClass, modelName + "[" + index + "]");
            if (m != null) {
                list.add(m);
            }
        }
        return list;
    }

    /**
     * 提取model对象数组的标号
     */
    private List<String> getIndexes(String modelName) {
        // 提取标号
        List<String> list = new ArrayList<String>();
        String modelNameAndLeft = modelName + "[";
        Map<String, String[]> parasMap = getRequest().getParameterMap();
        for (Map.Entry<String, String[]> e : parasMap.entrySet()) {
            String paraKey = e.getKey();
            if (paraKey.startsWith(modelNameAndLeft)) {
                String no = paraKey.substring(paraKey.indexOf("[") + 1,
                        paraKey.indexOf("]"));
                if (!list.contains(no)) {
                    list.add(no);
                }
            }
        }
        return list;
    }

    private static final int timeStampLen = "2011-01-18 16:18:18".length();
    private static final String timeStampPattern = "yyyy-MM-dd HH:mm:ss";
    private static final String datePattern = "yyyy-MM-dd";

    /**
     * test for all types of mysql
     *
     * 表单提交测试结果:
     * 1: 表单中的域,就算不输入任何内容,也会传过来 "", 也即永远不可能为 null.
     * 2: 如果输入空格也会提交上来
     * 3: 需要考 model中的 string属性,在传过来 "" 时是该转成 null还是不该转换,
     *    我想, 因为用户没有输入那么肯定是 null, 而不该是 ""
     *
     * 注意: 1:当clazz参数不为String.class, 且参数s为空串blank的情况,
     *       此情况下转换结果为 null, 而不应该抛出异常
     *      2:调用者需要对被转换数据做 null 判断，参见 ModelInjector 的两处调用
     */
    final Object convert(Class<?> clazz, String s) throws ParseException {
        // mysql type: varchar, char, enum, set, text, tinytext, mediumtext, longtext
        if (clazz == String.class) {
            return (StringUtils.isEmpty(s) ? null : s);	// 用户在表单域中没有输入内容时将提交过来 "", 因为没有输入,所以要转成 null.
        }
        s = s.trim();
        if (StringUtils.isEmpty(s)) {	// 前面的 String跳过以后,所有的空字符串全都转成 null,  这是合理的
            return null;
        }
        // 以上两种情况无需转换,直接返回, 注意, 本方法不接受null为 s 参数(经测试永远不可能传来null, 因为无输入传来的也是"")

        Object result = null;
        // mysql type: int, integer, tinyint(n) n > 1, smallint, mediumint
        if (clazz == Integer.class || clazz == int.class) {
            result = Integer.parseInt(s);
        }
        // mysql type: bigint
        else if (clazz == Long.class || clazz == long.class) {
            result = Long.parseLong(s);
        }
        // 经测试java.util.Data类型不会返回, java.sql.Date, java.sql.Time,java.sql.Timestamp 全部直接继承自 java.util.Data, 所以 getDate可以返回这三类数据
        else if (clazz == java.util.Date.class) {
            if (s.length() >= timeStampLen) {	// if (x < timeStampLen) 改用 datePattern 转换，更智能
                // Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
                // result = new java.util.Date(java.sql.Timestamp.valueOf(s).getTime());	// error under jdk 64bit(maybe)
                result = new SimpleDateFormat(timeStampPattern).parse(s);
            }
            else {
                // result = new java.util.Date(java.sql.Date.valueOf(s).getTime());	// error under jdk 64bit
                result = new SimpleDateFormat(datePattern).parse(s);
            }
        }
        // mysql type: date, year
        else if (clazz == java.sql.Date.class) {
            if (s.length() >= timeStampLen) {	// if (x < timeStampLen) 改用 datePattern 转换，更智能
                // result = new java.sql.Date(java.sql.Timestamp.valueOf(s).getTime());	// error under jdk 64bit(maybe)
                result = new java.sql.Date(new SimpleDateFormat(timeStampPattern).parse(s).getTime());
            }
            else {
                // result = new java.sql.Date(java.sql.Date.valueOf(s).getTime());	// error under jdk 64bit
                result = new java.sql.Date(new SimpleDateFormat(datePattern).parse(s).getTime());
            }
        }
        // mysql type: time
        else if (clazz == java.sql.Time.class) {
            result = java.sql.Time.valueOf(s);
        }
        // mysql type: timestamp, datetime
        else if (clazz == java.sql.Timestamp.class) {
            result = java.sql.Timestamp.valueOf(s);
        }
        // mysql type: real, double
        else if (clazz == Double.class) {
            result = Double.parseDouble(s);
        }
        // mysql type: float
        else if (clazz == Float.class) {
            result = Float.parseFloat(s);
        }
        // mysql type: bit, tinyint(1)
        else if (clazz == Boolean.class) {
            result = Boolean.parseBoolean(s) || "1".equals(s);
        }
        // mysql type: decimal, numeric
        else if (clazz == java.math.BigDecimal.class) {
            result = new java.math.BigDecimal(s);
        }
        // mysql type: unsigned bigint
        else if (clazz == java.math.BigInteger.class) {
            result = new java.math.BigInteger(s);
        }
        // mysql type: binary, varbinary, tinyblob, blob, mediumblob, longblob. I have not finished the test.
        else if (clazz == byte[].class) {
            result = s.getBytes();
        }
        else {
            throw new RuntimeException(clazz.getName() + " can not be converted, please use other type of attributes in your model!");
        }

        return result;
    }

    public Pageable getPageable(){
        Pageable pageable = new Pageable();

        Integer pageSize = getParaToInt("pageSize");
        if(pageSize == null){
            pageable.setPageSize(Pageable.DEFAULT_PAGE_SIZE);
        }else{
            pageable.setPageSize(pageSize);
        }

        Integer pageNumber = getParaToInt("pageNumber");
        if(pageSize == null){
            pageable.setPageNumber(Pageable.DEFAULT_PAGE_NUMBER);
        }else{
            pageable.setPageNumber(pageNumber);
        }

        String searchProperty = getPara("searchProperty");
        String searchValue = getPara("searchValue");
        if(StringUtils.isNotBlank(searchProperty) && StringUtils.isNotBlank(searchValue)){
            pageable.setSearchProperty(searchProperty);
            pageable.setSearchValue(searchValue);

            List<Filter> filters = Lists.newArrayList();
            Filter filter = new Filter();
            filter.setProperty(searchProperty);
            filter.setValue(searchValue);
            filter.setOperator(Filter.Operator.like);
            filters.add(filter);

            pageable.setFilters(filters);
        }

        String orderProperty = getPara("orderProperty");
        String orderDirection = getPara("orderDirection");
        if(StringUtils.isNotBlank(orderProperty) && StringUtils.isNotBlank(orderDirection)){
            pageable.setOrderProperty(orderProperty);
            if(Order.Direction.asc.name().equals(orderDirection)){
                pageable.setOrderDirection(Order.Direction.asc);
            }else{
                pageable.setOrderDirection(Order.Direction.desc);
            }

            List<Order> orders = Lists.newArrayList();
            Order order = new Order();
            order.setProperty(orderProperty);
            order.setDirection(pageable.getOrderDirection());
            orders.add(order);

            pageable.setOrders(orders);
        }

        //TODO filter and Order


        return pageable;
    }

}
